package com.ranyk.www.common.security;

import com.ranyk.www.entity.AdminUser;
import com.ranyk.www.enums.StatusEnum;
import com.ranyk.www.pojo.dto.UserRoleDTO;
import com.ranyk.www.security.AuthorizingUser;
import com.ranyk.www.service.IAdminUserService;
import com.ranyk.www.service.IRoleMenuService;
import com.ranyk.www.service.IUserRoleService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * CLASS_NAME: SystemAuthorizingRealm.java <br/>
 *
 * @author ranyk           <br/>
 * @version V1.0           <br/>
 * @decription: SystemAuthorizingRealm shiro 认证回调及授权查询回调函数实现类  <br/>
 * @date: 2022-04-14 <br/>
 */
@Component
public class SystemAuthorizingRealm extends AuthorizingRealm {

    private IAdminUserService adminUserService;
    private IUserRoleService userRoleService;
    private IRoleMenuService roleMenuService;

    @Autowired
    public void setAdminUserService(IAdminUserService adminUserService) {
        this.adminUserService = adminUserService;
    }

    @Autowired
    public void setUserRoleService(IUserRoleService userRoleService) {
        this.userRoleService = userRoleService;

    }

    @Autowired
    public void setRoleMenuService(IRoleMenuService roleMenuService) {
        this.roleMenuService = roleMenuService;
    }

    /**
     * Retrieves the AuthorizationInfo for the given principals from the underlying data store.  When returning
     * an instance from this method, you might want to consider using an instance of
     * {@link SimpleAuthorizationInfo SimpleAuthorizationInfo}, as it is suitable in most cases.
     *
     * @param principalCollection the primary identifying principals of the AuthorizationInfo that should be retrieved.
     * @return the AuthorizationInfo associated with this principals.
     * @see SimpleAuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        AuthorizingUser authorizingUser = (AuthorizingUser) principalCollection.getPrimaryPrincipal();
        if (authorizingUser != null) {
            //权限信息对象info,用来存放查出的用户的所有的角色（role）及权限（permission）
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            //获得用户角色列表
            UserRoleDTO userRoleDTO = userRoleService.getByUserId(authorizingUser.getUserId(), StatusEnum.NORMAL.getStatus());
            simpleAuthorizationInfo.addRoles(userRoleDTO.getRoleSigns());
            //获得权限列表
            Set<String> roleMenus = roleMenuService.getByRolesId(userRoleDTO.getRoleIds(), StatusEnum.NORMAL.getStatus());
            simpleAuthorizationInfo.addStringPermissions(roleMenus);
            return simpleAuthorizationInfo;
        }
        return null;
    }

    /**
     * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
     * authentication token.
     * <p/>
     * For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
     * more and letting Shiro do the rest.  But in some systems, this method could actually perform EIS specific
     * log-in logic in addition to just retrieving data - it is up to the Realm implementation.
     * <p/>
     * A {@code null} return value means that no account could be associated with the specified token.
     *
     * @param authenticationToken the authentication token containing the user's principal and credentials.
     * @return an {@link AuthenticationInfo} object containing account data resulting from the
     * authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.)
     * @throws AuthenticationException if there is an error acquiring data or performing
     *                                 realm-specific authentication logic for the specified <tt>token</tt>
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 获取基于用户名和密码的令牌：实际上这个 authenticationToken 是从 LoginController 里面 currentUser.login(token) 传过来的
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        AdminUser user = adminUserService.getByLoginName(token.getUsername());
        if (user == null) {
            // 没找到帐号
            throw new UnknownAccountException();
        }
        if (StatusEnum.FREEZE.getStatus().equals(user.getStatus())) {
            // 校验用户状态
            throw new DisabledAccountException();
        }
        AuthorizingUser authorizingUser = new AuthorizingUser();
        BeanUtils.copyProperties(user, authorizingUser);
        // 认证缓存信息
        return new SimpleAuthenticationInfo(authorizingUser, user.getLoginPassword(), ByteSource.Util.bytes(authorizingUser.getCredentialsSalt()), getName());
    }
}
